/*
 * SoapUI, Copyright (C) 2004-2022 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.wsdl.panels.teststeps.amf;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.TestCaseConfig;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.impl.wsdl.teststeps.AMFRequestTestStep;
import com.eviware.soapui.model.iface.Submit;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.iface.SubmitListener;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.types.StringToStringsMap;
import flex.messaging.io.amf.client.exceptions.ClientStatusException;
import flex.messaging.io.amf.client.exceptions.ServerStatusException;

import java.util.List;
import java.util.concurrent.Future;

public class AMFSubmit implements Submit, Runnable {
    public static final String AMF_CONNECTION = "AMF_CONNECTION";
    private volatile Future<?> future;
    private SubmitContext context;
    private Status status;
    private SubmitListener[] listeners;
    private Exception error;
    private long timestamp;
    private final AMFRequest request;
    private AMFResponse response;
    private AMFCredentials credentials;

    public AMFSubmit(AMFRequest request, SubmitContext submitContext, boolean async) {
        this.request = request;
        this.context = submitContext;

        List<SubmitListener> regListeners = SoapUI.getListenerRegistry().getListeners(SubmitListener.class);

        SubmitListener[] submitListeners = request.getSubmitListeners();
        this.listeners = new SubmitListener[submitListeners.length + regListeners.size()];
        for (int c = 0; c < submitListeners.length; c++) {
            this.listeners[c] = submitListeners[c];
        }

        for (int c = 0; c < regListeners.size(); c++) {
            this.listeners[submitListeners.length + c] = regListeners.get(c);
        }

        error = null;
        status = Status.INITIALIZED;
        timestamp = System.currentTimeMillis();

        if (async) {
            future = SoapUI.getThreadPool().submit(this);
        } else {
            run();
        }
    }

    public void cancel() {
        if (status == Status.CANCELED) {
            return;
        }

        SoapUI.log.info("Canceling request..");

        status = Status.CANCELED;

        for (int i = 0; i < listeners.length; i++) {
            try {
                listeners[i].afterSubmit(this, context);
            } catch (Throwable e) {
                SoapUI.logError(e);
            }
        }
    }

    public Status waitUntilFinished() {
        if (future != null) {
            if (!future.isDone()) {
                try {
                    future.get();
                } catch (Exception e) {
                    SoapUI.logError(e);
                }
            }
        } else {
            throw new RuntimeException("cannot wait on null future");
        }

        return getStatus();
    }

    public void run() {
        try {
            for (int i = 0; i < listeners.length; i++) {
                if (!listeners[i].beforeSubmit(this, context)) {
                    status = Status.CANCELED;
                    SoapUI.log.error("listener cancelled submit..");
                    return;
                }
            }

            status = Status.RUNNING;
            Object responseContent = executeAmfCall(getRequest());
            createResponse(responseContent);

            if (status != Status.CANCELED && status != Status.ERROR) {
                status = Status.FINISHED;
            }
        } catch (Exception e) {
            UISupport.showErrorMessage("There's been an error in executing query " + e.toString());
            error = e;
        } finally {

            if (status != Status.CANCELED) {
                for (int i = 0; i < listeners.length; i++) {
                    try {
                        listeners[i].afterSubmit(this, context);
                    } catch (Throwable e) {
                        SoapUI.logError(e);
                    }
                }
            }
        }
    }

    protected void createResponse(Object responseContent) {
        try {
            response = new AMFResponse(request, context, responseContent);
            response.setTimestamp(timestamp);
            response.setTimeTaken(System.currentTimeMillis() - timestamp);
        } catch (Exception e) {
            SoapUI.logError(e);
        }

    }

    private Object executeAmfCall(AMFRequest amfRequest) throws ClientStatusException, ServerStatusException {
        SoapUIAMFConnection amfConnection = null;
        try {
            amfConnection = getConnection(amfRequest);
            addAmfHeaders(amfRequest, amfConnection);
            addHttpHeaders(amfRequest, amfConnection);
            Object result = amfConnection.call(context, amfRequest.getAmfCall(), amfRequest.argumentsToArray());

            return result;
        } catch (Exception e) {
            SoapUI.logError(e);
            error = e;
            status = Status.ERROR;
        } finally {
            amfRequest.clearArguments();
            if (context.getModelItem() instanceof AMFRequestTestStep) {
                if (credentials != null && credentials.isLoggedIn()) {
                    credentials.logout();
                    credentials = null;
                } else {
                    amfConnection.close();
                }
            }
        }
        return null;

    }

    private SoapUIAMFConnection getConnection(AMFRequest amfRequest) throws Exception {
        SoapUIAMFConnection amfConnection = null;
        if (isAuthorisationEnabled(amfRequest) && (context.getModelItem() instanceof WsdlTestCase)) {
            if ((amfConnection = (SoapUIAMFConnection) context.getProperty(AMF_CONNECTION)) != null) {
                return amfConnection;
            } else {
                throw new Exception("amf session connection error! ");
            }
        } else if (isAuthorisationEnabled(amfRequest) && (context.getModelItem() instanceof AMFRequestTestStep)) {
            String endpoint = context.expand(getTestCaseConfig(amfRequest).getAmfEndpoint());
            String username = context.expand(getTestCaseConfig(amfRequest).getAmfLogin());
            String password = context.expand(getTestCaseConfig(amfRequest).getAmfPassword());

            if (StringUtils.hasContent(endpoint) && StringUtils.hasContent(username)) {
                credentials = new AMFCredentials(endpoint, username, password, context);
                amfConnection = credentials.login();
            } else {
                amfConnection = new SoapUIAMFConnection();
                amfConnection.connect(context.expand(amfRequest.getEndpoint()));
            }

            context.setProperty(AMF_CONNECTION, amfConnection);
            return amfConnection;
        } else {
            amfConnection = new SoapUIAMFConnection();
            amfConnection.connect(context.expand(amfRequest.getEndpoint()));
            return amfConnection;
        }
    }

    private boolean isAuthorisationEnabled(AMFRequest amfRequest) {
        return getTestCaseConfig(amfRequest).getAmfAuthorisation();
    }

    private TestCaseConfig getTestCaseConfig(AMFRequest amfRequest) {
        return amfRequest.getTestStep().getTestCase().getConfig();
    }

    private void addHttpHeaders(AMFRequest amfRequest, SoapUIAMFConnection amfConnection) {
        StringToStringsMap httpHeaders = amfRequest.getHttpHeaders();
        if (httpHeaders != null) {
            for (String key : httpHeaders.getKeys()) {
                for (String value : httpHeaders.get(key)) {
                    amfConnection.addHttpRequestHeader(key, context.expand(value));
                }
            }
        }
    }

    private void addAmfHeaders(AMFRequest amfRequest, SoapUIAMFConnection amfConnection) {
        if (amfRequest.getAmfHeaders() != null) {
            for (String key : amfRequest.getAmfHeaders().keySet()) {
                Object data = amfRequest.getAmfHeaders().get(key);
                if (data instanceof String) {
                    data = context.expand((String) data);
                }

                amfConnection.addAmfHeader(key, data);
            }
        }
    }

    public Exception getError() {
        return error;
    }

    public AMFRequest getRequest() {
        return request;
    }

    public AMFResponse getResponse() {
        return response;
    }

    public Status getStatus() {
        return status;
    }

}
